﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
using System.Diagnostics.Contracts;

namespace System.Threading.Actors
{
    // The ActorEvent class provides a standardized wrapper for Actor event data.
    [Serializable]
    public class ActorEvent
    {
        public string EventSource { get; set; } // name of originating actor
        public string EventSourcePartitionName { get; set; } // Partition name of originating actor, if it was partitioned.
        public string EventType { get; /*private*/ set; }
        public string EventPayload { get; /*private*/ set; }
        public string[] EventPayloadQualifiers { get; /*private*/ set; }

        // For JSON Deserializer
        public ActorEvent()
        {
        }

        public ActorEvent(string eventType)
            : this(eventType, null, null)
        {
        }

        public ActorEvent(string eventType, object eventPayload)
            : this(eventType, eventPayload, null)
        {
        }

        public ActorEvent(string eventType, object eventPayload, params object[] eventPayloadQualifiers)
        {
            EventType = eventType;
            if(eventPayload != null) EventPayload = JsonConvert.SerializeObject(eventPayload);
            if (eventPayloadQualifiers != null)
            {
                var qualifiers = new string[eventPayloadQualifiers.Length];
                for (int i = 0; i < eventPayloadQualifiers.Length; i++)
                {
                    qualifiers[i] = JsonConvert.SerializeObject(eventPayloadQualifiers[i]);
                }
                EventPayloadQualifiers = qualifiers;
            }
        }

        // Allow an ActorEvent creator to provide a custom serializer for the payload
        public static ActorEvent FromPayload<T>(string eventType, string partitionName, T payload, Func<T, string> serializer)
        {
            Contract.Requires(serializer != null);

            ActorEvent ae = new ActorEvent();
            ae.EventType = eventType;
            ae.EventSourcePartitionName = partitionName;
            ae.EventPayloadQualifiers = null; // TODO: Why is this null?  Do we put everything into the payload by convention?
            ae.EventPayload = serializer(payload);
            return ae;
        }

        // TODO: Consider caching this result
        public T GetTypedPayload<T>()
        {
            if (EventPayload == null)
                throw new InvalidOperationException("Called GetTypedPayload with null EventPayload");
            return JsonConvert.DeserializeObject<T>(EventPayload);
        }

        // TODO: Consider caching results
        public T GetTypedPayloadQualifier<T>(int index)
        {
            Contract.Requires(index >= 0);
            Contract.Requires(index < this.EventPayloadQualifiers.Length);

            if (EventPayloadQualifiers == null)
                throw new InvalidOperationException("Called GetTypePayloadQualifier with null EventPayloadQualifiers");
            return JsonConvert.DeserializeObject<T>(EventPayloadQualifiers[index]);
        }

        // Allow for easy access to human-readable description
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendFormat("Source={0}", EventSource);
            sb.AppendFormat(",Partition={0}", EventSourcePartitionName);
            sb.AppendFormat(",Type={0}", EventType);
            if (EventPayload != null)
            {
                sb.AppendFormat(",Payload={0}(", EventPayload);
                if (EventPayloadQualifiers != null)
                {
                    for (int i = 0; i < EventPayloadQualifiers.Length; i++)
                    {
                        if (i > 0) sb.Append(",");
                        sb.Append(EventPayloadQualifiers[i]);
                    }
                }
                sb.Append(")");
            }

            return sb.ToString();
        }

        // Provide a fast JSON serializer for ActorEvent
        public string ToJson()
        {
            var sw = new StringWriter();
            var writer = new JsonTextWriter(sw);

            writer.WriteStartObject();

            if (EventSource != null)
            {
                writer.WritePropertyName("EventSource"); writer.WriteValue(EventSource);
            }

            if (EventSourcePartitionName != null)
            {
                writer.WritePropertyName("EventSourcePartitionName"); writer.WriteValue(EventSourcePartitionName);
            }

            if (EventType != null)
            {
                writer.WritePropertyName("EventType"); writer.WriteValue(EventType);
            }

            if (EventPayload != null)
            {
                writer.WritePropertyName("EventPayload"); writer.WriteValue(EventPayload);
            }

            if (EventPayloadQualifiers != null)
            {
                writer.WritePropertyName("EventPayloadQualifiers");
                writer.WriteStartArray();
                foreach (var arg in EventPayloadQualifiers) writer.WriteValue(arg);
                writer.WriteEndArray();
            }

            writer.WriteEndObject();

            return sw.ToString();
        }

        // Provide a fast JSON deserializer for ActorEvent
        public static ActorEvent FromJson(string json)
        {
            Contract.Requires(json != null);

            var sr = new StringReader(json);
            var reader = new JsonTextReader(sr);
            var ae = new ActorEvent();
            while (reader.Read())
            {
                if (reader.TokenType == JsonToken.PropertyName)
                {
                    var name = (string)reader.Value;
                    if (name.Equals("EventSource"))
                    {
                        reader.Read();
                        ae.EventSource = (string)reader.Value;
                    }
                    else if (name.Equals("EventSourcePartitionName"))
                    {
                        reader.Read();
                        ae.EventSourcePartitionName = (string)reader.Value;
                    }
                    else if (name.Equals("EventType"))
                    {
                        reader.Read();
                        ae.EventType = (string)reader.Value;
                    }
                    else if (name.Equals("EventPayload"))
                    {
                        reader.Read();
                        ae.EventPayload = (string)reader.Value;
                    }
                    else if (name.Equals("EventPayloadQualifiers"))
                    {
                        reader.Read(); // This token will either be "null" or a begin-array token
                        if (reader.TokenType != JsonToken.Null) // it's a begin-array
                        {
                            var argList = new List<string>();
                            reader.Read(); // Skip past the begin-array token
                            while (reader.TokenType != JsonToken.EndArray)
                            {
                                argList.Add((string)reader.Value);
                                reader.Read();
                            }
                            ae.EventPayloadQualifiers = argList.ToArray();
                        }
                    }
                }
            }

            return ae;
        }

    }

}
